#!/usr/bin/env jruby

require 'test/unit'
dir = File.dirname(__FILE__)
require File.join(dir, '..', '..', 'lib', 'auxil', 'extent')

########################################

module Constants4ExtentTests
  INTEGER, FLOAT, STRING, TIME = 12345, 123.45, 'foo', Time.now
  
  E0 =   Extent.new(  0)
  E2 =   Extent.new(  2)
  E3 =   Extent.new(  3)
  E3p3 = Extent.new(3.3)
  E3p4 = Extent.new(3.4)
  E5 =   Extent.new(  5)
  E123 = Extent.new(123)
  
  DELTA = 1e-6
end

########################################

class TestExtentInitialize < Test::Unit::TestCase
  include Constants4ExtentTests
  
  def test_should_accept_numeric
    assert_nothing_thrown('i') { Extent.new(INTEGER) }
    assert_nothing_thrown('f') { Extent.new(FLOAT) }
  end
  
  def test_reject_nonnumeric
    assert_raise(TypeError, 's') { Extent.new(STRING) }
    assert_raise(TypeError, 't') { Extent.new(TIME) }
  end
  
  def test_should_store_correct_value
    assert_equal INTEGER, Extent.new(INTEGER).to_i
    assert_equal FLOAT, Extent.new(FLOAT).to_f
  end
end

########################################

class TestExtentCompare < Test::Unit::TestCase
  include Constants4ExtentTests
  
  def test_should_accept_extent
    assert_nothing_thrown { E0 <=> E123 }
  end
  
  def test_should_accept_numeric
    assert_nothing_thrown('i') { E0 <=> INTEGER }
    assert_nothing_thrown('f') { E0 <=> FLOAT }
  end
  
  def test_should_reject_others
    assert_raise(TypeError, 's') { E0 <=> STRING }
    assert_raise(TypeError, 't') { E0 <=> TIME }
  end
  
  def test_should_compare_extent
    assert E3p3 < E3p4
  end
  
  def test_should_compare_numeric
    assert E123 < 124, '124'
    assert E123 == 123.0, '123.0'
    assert E123 > 122.9, '122.9'
  end
  
  def test_should_compare_numeric_coercion
    assert 123.1 > E123
  end
end

########################################

class TestExtentAddSubtract < Test::Unit::TestCase
  include Constants4ExtentTests
  
  OPERATORS = [:+, :-]
  
  def test_should_accept_extent
    OPERATORS.each do |op|
      assert_nothing_thrown("#{op}") { E2.send(op, E5) }
    end
  end
  
  def test_should_reject_nonextent
    OPERATORS.each do |op|
      assert_raise(TypeError, "#{op} i") { E2.send(op, INTEGER) }
      assert_raise(TypeError, "#{op} f") { E2.send(op, FLOAT) }
      assert_raise(TypeError, "#{op} s") { E2.send(op, STRING) }
      assert_raise(TypeError, "#{op} t") { E2.send(op, TIME) }
    end
  end
  
  def test_should_reject_numeric_coercion
    OPERATORS.each do |op|
      assert_raise(TypeError, "#{op} i") { INTEGER.send(op, E5) }
      assert_raise(TypeError, "#{op} f") { FLOAT.send(op, E5) }
    end
  end
  
  def test_should_return_extent
    OPERATORS.each do |op|
      assert_kind_of(Extent, E2.send(op, E5), "#{op}")
    end
  end
  
  def test_should_add_values
    assert_equal 7, (E2 + E5).to_i
    assert_in_delta 6.7, (E3p3 + E3p4).to_f, DELTA
  end
  
  def test_should_subtract_values
    assert_equal -3, (E2 - E5).to_i
    assert_in_delta -0.1, (E3p3 - E3p4).to_f, DELTA
  end
end

########################################

class TestExtentMultiply < Test::Unit::TestCase
  include Constants4ExtentTests
  
  def test_should_accept_numeric
    assert_nothing_thrown('i') { E2 * INTEGER }
    assert_nothing_thrown('f') { E2 * FLOAT }
  end
  
  def test_should_reject_extent
    assert_raise(TypeError) { E2 * E5 }
  end
  
  def test_should_reject_others
    assert_raise(TypeError, 's') { E2 * STRING }
    assert_raise(TypeError, 't') { E2 * TIME }
  end
  
  def test_should_accept_numeric_coercion
    assert_nothing_thrown('i') { INTEGER * E5 }
    assert_nothing_thrown('f') { FLOAT * E5 }
  end
  
  def test_should_return_extent
    assert_kind_of Extent, E2 * INTEGER, 'i normal'
    assert_kind_of Extent, INTEGER * E2, 'i coerce'
    assert_kind_of Extent, E2 * FLOAT, 'f normal'
    assert_kind_of Extent, FLOAT * E2, 'f coerce'
  end
  
  def test_should_multiply_values
    assert_equal 10, (E2 * 5).to_i, 'normal'
    assert_equal 10, (2 * E5).to_i, 'coerce'
    assert_in_delta 6.8, (E3p4 * 2).to_f, DELTA, 'normal'
    assert_in_delta 6.8, (3.4 * E2).to_f, DELTA, 'coerce'
  end
end

########################################

class TestExtentDivide < Test::Unit::TestCase
  include Constants4ExtentTests
  
  def test_should_accept_numeric
    assert_nothing_thrown('i') { E5 / INTEGER }
    assert_nothing_thrown('f') { E5 / FLOAT }
  end
  
  def test_should_accept_extent
    assert_nothing_thrown { E5 / E3 }
  end
  
  def test_should_reject_others
    assert_raise(TypeError, 's') { E5 / STRING }
    assert_raise(TypeError, 't') { E5 / TIME }
  end
  
  def test_should_reject_numeric_coercion
    assert_raise(TypeError, 'i') { INTEGER / E5 }
    assert_raise(TypeError, 'f') { FLOAT / E5 }
  end
  
  def test_should_return_extent
    assert_kind_of Extent, E5 / INTEGER, 'i'
    assert_kind_of Extent, E5 / FLOAT, 'f'
  end
  
  def test_should_return_numeric
    assert_kind_of Integer, E5 / E3
    assert_kind_of Float, E3p4 / E2, '1.7'
    assert_kind_of Float, E5 / Extent.new(2.0), '2.5'
  end
  
  def test_should_divide_values
    assert_in_delta 2, (E5 / 2).to_f, DELTA, '2'
    assert_in_delta 2, (E5 / E2).to_f, DELTA, 'E2'
    assert_in_delta 2.5, (E5 / 2.0).to_f, DELTA
    assert_in_delta 1.7, (E3p4 / 2).to_f, DELTA, '2'
    assert_in_delta 1.7, (E3p4 / E2).to_f, DELTA, 'E2'
  end
end
